<?php

	/************************************************************\
	 *      wordCloud - Lisa Miller 2009
	 *      modified version of:
	 *	wordCloud Copyright 2007 Derek Harvey
	 *	www.lotsofcode.com
	 *
	 *	Uses an implementation of the Porter Stemming Alorithm
	 *	to group related words into one count kept under the
	 *	stem of those words.
	 *
	 *	The cloud itself is stored as an associative array, wordsArray
	 *	which has the form:
	 *		stem => array( word, url, color, count, range)
	 *
	 *		word = the shortest version of the words seen with that stem
	 *		url = the search url and any other search terms to be included in the
	 *			linked search when cloud is displayed
	 *		color = if null is controlled by the style sheet, otherwise will be display
	 *			color of the word
	 *		count = the number of times a word is seen, can be adjusted to make
	 *			certain words more important
	 *		range = the calculated font size of the word
	 *
	 *
	 \************************************************************/
	require_once("class.stemmer.inc");

	class wordCloud
	{

		private $wordsArray;	//The data is stored in this associative array
		private $removes;	//array of words to remove from the word cloud
		private $stemmer;	//stemmer object for extracting word stems
		private $countLimit;	//if set, words with a count below this limit will be removed from the cloud
		private $wordLimit;	//if set, limits the total number of words in the cloud
		private $maxCount;



		public function __construct()
		{
		     $this->wordsArray = array();
		     $this->removes = array();
		     $this->stemmer = new Stemmer();
		     $this->countLimit = NULL;
		     $this->wordLimit = NULL;
		     $this->maxCount = 0;

		}

	       public function __get($name)
	       {
		    return $this->$name;
	       }

	       public function __set($attribute, $value)
	       {
		    $this->$attribute = $value;
	       }

		/*
		 * Assign word to array of stems
		 * Required parameter: $word -- a single word string
		 * Optional parameters:
		 *		$url -- a websearch url with any additional
		 *			desired terms (other than this word)
		 *		$color -- color word will appear in the cloud,
		 *			defaults to grey/black of stylesheet
		 *		$count -- defaults to 1, bigger counts can be
		 *			added to make a word larger in the cloud
		 *
		 */

		public function addWord($word, $url=NULL, $color=NULL, $count = 1)
		{

			$word = trim(strtolower($word));
			$stem = $this->stemmer->stem($word);


			//if word isn't in the cloud yet add it
			if (empty($this->wordsArray[$stem])) {
				$this->wordsArray[$stem] = array();
			}

			 //check to see if saved word is shortest
			//keep shortest version
			if(!empty($this->wordsArray[$stem]['word']))
			{
			     if(strlen($word) < strlen($this->wordsArray[$stem]['word']))
			     {
				  //replace the current saved word with a shorter one
				  $this->wordsArray[$stem]['word'] = $word;
			     }
			}else{
			     $this->wordsArray[$stem]['word'] = $word;
			}
			//echo 'Added stem:'.$stem.'as word:'.$this->wordsArray[$stem]['word']. ' with count '.$count.'<br />';
			$this->wordsArray[$stem]['count'] += $count;
			$this->wordsArray[$stem]['url'] = $url.$this->wordsArray[$stem]['word'];
			$this->wordsArray[$stem]['color'] = $color;
			//echo 'Color = '.$this->wordsArray[$stem]['color'].':<br />';
			$this->wordsArray[$stem]['range'] = 0;

			return $this->wordsArray[$stem];
		}

		/* Sets a lower limit for the count of */
		/* words included in cloud.            */
		/* ie: if set to 3, only words seen 3  */
		/* or more times will be included in   */
		/* the cloud array                     */
		public function setCountLimit($limit)
		{
			$this->countLimit = $limit;
		}

		/* Sets a limit on the number of words */
		/* included in the cloud.              */
		/* only the $limit number of words with*/
		/* the highest seen counts will be incl.*/
		public function setWordLimit($limit)
		{
		     $this->wordLimit = $limit;
		}

	       //adds word to array to be taken out of wordCloud
		public function removeWord($word)
		{
			$this->removes[] = strtolower($word);
		}


		/*
		 * Removes "removed" words from the wordsArray
		 * Performs any desired limiting of cloud,
		 *	by individual word counts or by overall
		 *	number of words allowed in the cloud.
		 *
		 * Calculates font sizes based on a partitioning of the
		 * difference between the smallest word count and the
		 * largest into 10 bins. This method guarantees that
		 * the words in the cloud span the whole 9 font-size
		 * range of the CSS.
		 * some words will be tiny and some large
		 *
		 * Shuffles the words with PHP shuffle function by default
		 *
		 * Returns cloud as an associative array
		 *
		 */

		public function getCloud($shuffle = true)
		{

			if (!empty($this->countLimit))
			{
				//echo 'Calling limitCounts<br />';
				$this->limitCounts();
			}
			if (!empty($this->removes))
			{
				//echo 'Calling removeWords <br />';
				$this->removeWords();
			}
			if (!empty($this->wordLimit))
			{
			     //echo 'Calling limitWords<br />';
			     $this->limitWords();
			}
			if($shuffle)
			{
			      $this->shuffleCloud();
			}
			$this->partitionRanges();

			return $this->wordsArray;
		}


		/*
		 * "Old way" of sizing words is used
		 * Removes "removed" words from the wordsArray
		 * Performs any desired limiting of cloud,
		 *	by individual word counts or by overall
		 *	number of words allowed in the cloud.
		 *
		 * Calculates font sizes of words based on (count/max count) * 100
		 *
		 * Shuffles the words with PHP shuffle function by default
		 *
		 * Returns cloud as an associative array
		 *
		 */

		public function getCloudNoPartition($shuffle = true)
		{

			if (!empty($this->countLimit))
			{
				//echo 'Calling limitCounts<br />';
				$this->limitCounts();
			}
			if (!empty($this->removes))
			{
				//echo 'Calling removeWords <br />';
				$this->removeWords();
			}
			if (!empty($this->wordLimit))
			{
			     //echo 'Calling limitWords<br />';
			     $this->limitWords();
			}
			if($shuffle)
			{
			      $this->shuffleCloud();
			}

			$this->maxCount = $this->getMax();

			//calculate the font size ranges
			foreach ($this->wordsArray as $stem => $data)
			{
			      if(is_array($data)) {
					$sizeRange = $this->getClassFromPercent(($data['count'] / $this->maxCount) * 100);
					$this->wordsArray[$stem]['range'] = $sizeRange;

			      }

			}
			return $this->wordsArray;
		}


/**************** Private Methods ****************************************/


	       /* Partition counts into 10 size ranges */
	       private function partitionRanges()
	       {
		    $maximum = $this->getMax();
		    $minimum = $this->getMin();
		    $increment = ($maximum - $minimum)/9;

			foreach ($this->wordsArray as $stem => $data)
			{
			      if(is_array($data)) {
					$sizeRange = (int)(($data['count']-$minimum) / $increment);
					$this->wordsArray[$stem]['range'] = $sizeRange;

			      }

			}

	       }

		/*
		 * Shuffle associated names in array
		 * using PHP array shuffle of the keys/stems
		 *
		 */

		private function shuffleCloud()
		{
			$stems = array_keys($this->wordsArray);
			shuffle($stems);
			if (count($stems) && is_array($stems))
			{
				$tmpArray = $this->wordsArray;
				$this->wordsArray = array();
				foreach ($stems as $stem)
				{
					$this->wordsArray[$stem] = $tmpArray[$stem];

				}
			}
		}


		/*
		 * Get the class range (font size) using a percentage
		 *
		 * @returns int $class
		 */

		private function getClassFromPercent($percent)
		{
			if ($percent >= 99)
				$class = 9;
			elseif ($percent >= 70)
				$class = 8;
			elseif ($percent >= 60)
				$class = 7;
			elseif ($percent >= 50)
				$class = 6;
			elseif ($percent >= 40)
				$class = 5;
			elseif ($percent >= 30)
				$class = 4;
			elseif ($percent >= 20)
				$class = 3;
			elseif ($percent >= 10)
				$class = 2;
			elseif ($percent >= 5)
				$class = 1;
			else
				$class = 0;
			return $class;
		}


		/*
		 * Removes words with count less than limit value
		 */

		private function limitCounts()
		{
			foreach ($this->wordsArray as $stem => $info)
			{
				if ($this->countLimit >= $info['count'])
				{
					unset($this->wordsArray[$stem]);
				}
			}
		}

		/* Removes all but the words with the highest counts */
		/* Number of words preserved is set by the wordLimit */
		/* variable                                          */

		private function limitWords()
		{
		    $tempArray = $this->wordsArray;
		    //sort by word "count"
		    uasort($tempArray, 'wordCountCmp');
		    //chop off all but the top count words
		    $output = array_slice($tempArray, 0, $this->wordLimit, true);
		    //take everything else out of the array (maintains order)
		    //$result_array = array_intersect_assoc($this->wordsArray, $output);
		    $this->wordsArray = $output;
		    unset($tempArray);
		    unset($output);

		}


		//removes the list of removes from the cloud
		private function removeWords()
		{
			foreach ($this->removes as $badWord) {

			      $badStem = (string)$this->stemmer->stem($badWord);
			      if (array_key_exists($badStem, $this->wordsArray))
			      {
				   unset($this->wordsArray[$badStem]);
			      }
			}
		}


		/*
		 * Finds the maximum word count in words array
		 */

		private function getMax()
		{
			$max = 0;
			 foreach ($this->wordsArray as $stem => $data)
			 {
			      if ($data['count'] > $max)
			      {
				   $max = $data['count'];
			      }

			}
			return $max;
		}

		/*
		 * Finds the minimum word count in words array
		 */

		private function getMin()
		{
			$min = PHP_INT_MAX;
			 foreach ($this->wordsArray as $stem => $data)
			 {
			      if ($data['count'] < $min)
			      {
				   $min = $data['count'];
			      }

			}
			return $min;
		}



	}//end wordcloud class

	       //non-class function for sorting associative array by stem=>count
	       //using PHP uasort in limitWords method
	       function wordCountCmp($a , $b)
	       {
		    if ($a['count'] == $b['count']) {
			      return 0;
		    }
			 return ($a['count'] < $b['count']) ? 1 : -1;

	       }


?>